package top.gokzzz.common.utils;

import java.time.DateTimeException;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 日期時間 工具類
 *
 * @author Gok
 * @version 1.6
 */
public class DTUtil {

    /**
     * 默認系統時區
     */
    private static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault();

    /**
     * 日期时间格式 yyyy-MM-dd HH:mm:ss
     */
    public static final String NORM_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";

    /**
     * 日期时间格式 yyyy-MM-dd HH:mm
     */
    public static final String NORM_DATETIME_PATTERN2 = "yyyy-MM-dd HH:mm";

    /**
     * 日期时间格式 yyyy-MM-dd
     */
    public static final String NORM_DATE_PATTERN = "yyyy-MM-dd";

    /**
     * 日期时间格式 yyyy-MM-dd
     */
    public static final String EN_DATE_PATTERN = "dd/MM/yyyy";

    /**
     * 时间格式 HH:mm:ss
     */
    public static final String NORM_TIME_PATTERN = "HH:mm:ss";

    /**
     * 时间格式 HH:mm
     */
    public static final String NORM_TIME_MINUTE_PATTERN = "HH:mm";

    /**
     * 國外日期时间格式 dd/MM/yyyy HH:mm:ss
     */
    public static final String EN_DATETIME_PATTERN2 = "dd/MM/yyyy HH:mm:ss";

    /**
     * 國外日期时间格式 dd/MM/yyyy HH:mm
     */
    public static final String EN_DATETIME_PATTERN3 = "dd/MM/yyyy HH:mm";

    /**
     * Date -> String，使用默認 {@link #NORM_DATETIME_PATTERN}
     *
     * @param date 日期
     * @return {@link String }
     */
    public static String parse(Date date) {
        return parse(date, NORM_DATETIME_PATTERN);
    }

    /**
     * Date -> String，使用指定的日期格式
     *
     * @param date    日期
     * @param pattern 日期格式
     * @return {@link String }
     */
    public static String parse(Date date, String pattern) {
        return formatDate(date, pattern);
    }

    /**
     * LocalDateTime -> String，使用默認 {@link #NORM_DATETIME_PATTERN}
     *
     * @param localDateTime 日期
     * @return {@link String }
     */
    public static String parse(LocalDateTime localDateTime) {
        return parse(localDateTime, NORM_DATETIME_PATTERN);
    }

    /**
     * LocalDateTime -> String，使用指定的日期格式
     *
     * @param localDateTime 日期
     * @param pattern       日期格式
     * @return {@link String }
     */
    public static String parse(LocalDateTime localDateTime, String pattern) {
        if (localDateTime == null) {
            return "";
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        return localDateTime.format(formatter);
    }

    /**
     * LocalDate -> String，使用指定的日期格式
     *
     * @param localDate 日期
     * @return {@link String }
     */
    public static String parse(LocalDate localDate) {
        return parse(localDate, NORM_DATE_PATTERN);
    }

    /**
     * LocalDate -> String，使用指定的日期格式
     *
     * @param localDate 日期
     * @param pattern   日期格式
     * @return {@link String }
     */
    public static String parse(LocalDate localDate, String pattern) {
        if (localDate == null) {
            return "";
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        return localDate.format(formatter);
    }

    /**
     * LocalTime -> String，使用指定的日期格式
     *
     * @param localTime 時間
     * @return {@link String }
     */
    public static String parse(LocalTime localTime) {
        return parse(localTime, NORM_TIME_PATTERN);
    }

    /**
     * LocalTime -> String，使用指定的日期格式
     *
     * @param localTime 時間
     * @param pattern   日期格式
     * @return {@link String }
     */
    public static String parse(LocalTime localTime, String pattern) {
        if (localTime == null) {
            return "";
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        return localTime.format(formatter);
    }

    /**
     * Date -> LocalDateTime
     *
     * @param date 日期
     * @return {@link LocalDateTime }
     */
    public static LocalDateTime toLocalDateTime(Date date) {
        return toConvert(date, LocalDateTime.class);
    }

    /**
     * Date -> LocalDate
     *
     * @param date 日期
     * @return {@link LocalDate }
     */
    public static LocalDate toLocalDate(Date date) {
        return toConvert(date, LocalDate.class);
    }

    /**
     * Date -> LocalTime
     *
     * @param date 日期
     * @return {@link LocalTime }
     */
    public static LocalTime toLocalTime(Date date) {
        return toConvert(date, LocalTime.class);
    }

    /**
     * LocalDateTime -> Date
     *
     * @param localDateTime 日期
     * @return {@link LocalTime }
     */
    public static Date toDate(LocalDateTime localDateTime) {
        return localDateTime == null ? null : instant(localDateTime.atZone(DEFAULT_ZONE_ID).toInstant());
    }

    /**
     * LocalDate -> Date
     *
     * @param localDate 日期
     * @return {@link Date }
     */
    public static Date toDate(LocalDate localDate) {
        return localDate == null ? null : instant(localDate.atStartOfDay(DEFAULT_ZONE_ID).toInstant());
    }

    /**
     * 獲取時間併集區間
     *
     * @param fromTime   開始
     * @param toTime     結束
     * @param rangeStart 範圍開始
     * @param rangeEnd   範圍結束
     * @return {@link LocalTime[] } LocalTime[0] 開始，LocalTime[1] 結束
     */
    public static LocalTime[] getUnionTimeRange(LocalTime fromTime, LocalTime toTime, LocalTime rangeStart, LocalTime rangeEnd) {
        if (fromTime == null || toTime == null || rangeStart == null || rangeEnd == null) {
            return new LocalTime[0];
        }
        if (fromTime.isAfter(toTime) || rangeStart.isAfter(rangeEnd)) {
            throw new IllegalArgumentException("Invalid time range: fromTime must be before or equal to toTime.");
        }

        // 检查是否有重叠
        if (toTime.isBefore(rangeStart) || rangeEnd.isBefore(fromTime)) {
            return new LocalTime[0];
        } else {
            // 有重叠，计算并集的起始和结束时间
            LocalTime unionStart = fromTime.isAfter(rangeStart) ? fromTime : rangeStart;
            LocalTime unionEnd = toTime.isBefore(rangeEnd) ? toTime : rangeEnd;
            return new LocalTime[]{unionStart, unionEnd};
        }
    }

    /**
     * 計算 2 個時間段的分鐘數
     *
     * @param startTime 開始時間
     * @param endTime   結束時間
     * @return long
     */
    public static long getMinutesBetween(LocalTime startTime, LocalTime endTime) {
        if (startTime == null || endTime == null) return 0;
        return Duration.between(startTime, endTime).toMinutes();
    }

    /**
     * 獲取特定年份的開始和結束
     *
     * @param year 年份
     * @param type 类型 LocalDateTime、LocalDate、Date
     * @return {@link LocalDateTime[] } Object[0]：開始 Object[1]：結束
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] getYearStartAndEnd(int year, Class<T> type) {
        LocalDateTime startDateTime = LocalDateTime.of(year, 1, 1, 0, 0);
        LocalDateTime endDateTime = LocalDateTime.of(year, 12, 31, 23, 59, 59, 999999999);

        if (type == LocalDateTime.class) {
            return (T[]) new LocalDateTime[]{startDateTime, endDateTime};
        } else if (type == LocalDate.class) {
            LocalDate startDate = startDateTime.toLocalDate();
            LocalDate endDate = endDateTime.toLocalDate();
            return (T[]) new LocalDate[]{startDate, endDate};
        } else if (type == Date.class) {
            Date start = toDate(startDateTime);
            Date end = toDate(endDateTime);
            return (T[]) new Date[]{start, end};
        } else {
            throw new IllegalArgumentException("Unsupported type: " + type.getName());
        }
    }

    /**
     * 計算兩天的間隔天數
     *
     * @param date1 日期1
     * @param date2 日期2
     * @return int 間隔天數
     */
    public static int getDaysBetween(Date date1, Date date2) {
        LocalDate localDate1 = toLocalDate(date1);
        LocalDate localDate2 = toLocalDate(date2);
        return getDaysBetween(localDate1, localDate2);
    }

    /**
     * 計算兩天的間隔天數
     *
     * @param date1 日期1
     * @param date2 日期2
     * @return int 間隔天數
     */
    public static int getDaysBetween(LocalDate date1, LocalDate date2) {
        return (int) Math.abs(ChronoUnit.DAYS.between(date1, date2));
    }

    /**
     * 獲取當前月份的所有日
     *
     * @return 当前月份所有日期列表
     */
    public static List<LocalDate> getCurrentMonthDates() {
        return getMonthDays(
                LocalDate.now().getYear(),
                LocalDate.now().getMonthValue()
        );
    }

    /**
     * 獲取某年某月的所有日
     *
     * @param year  年份（>= 1970）
     * @param month 月份（1-12）
     * @return 所有日期列表
     * @throws DateTimeException 參數超出有效範圍
     */
    public static List<LocalDate> getMonthDays(int year, int month) {
        YearMonth yearMonth = YearMonth.of(year, month);
        return IntStream.rangeClosed(1, yearMonth.lengthOfMonth())
                .mapToObj(yearMonth::atDay)
                .collect(Collectors.toList());
    }

    /**
     * 判斷日期是否在特定日期範圍內
     *
     * @param localDate    日期
     * @param compareStart 比較日期開始
     * @param compareEnd   比較日期結束
     * @return boolean
     */
    public static boolean isBetween(LocalDate localDate, LocalDate compareStart, LocalDate compareEnd) {
        Objects.requireNonNull(localDate, "Target date cannot be null");
        Objects.requireNonNull(compareStart, "Start date cannot be null");
        Objects.requireNonNull(compareEnd, "End date cannot be null");

        // 自动处理日期顺序（若起始时间晚于结束时间则交换）
        if (compareStart.isAfter(compareEnd)) {
            LocalDate temp = compareStart;
            compareStart = compareEnd;
            compareEnd = temp;
        }

        // 判断是否在闭区间内 [compareStart, compareEnd]
        return !localDate.isBefore(compareStart) && !localDate.isAfter(compareEnd);
    }

    /**
     * 判斷日期時間是否在特定日期時間範圍內
     *
     * @param localDateTime 日期時間
     * @param compareStart  比較日期開始
     * @param compareEnd    比較日期結束
     * @return boolean
     */
    public static boolean isBetween(LocalDateTime localDateTime, LocalDateTime compareStart, LocalDateTime compareEnd) {
        Objects.requireNonNull(localDateTime, "Target date time cannot be null");
        Objects.requireNonNull(compareStart, "Start date time cannot be null");
        Objects.requireNonNull(compareEnd, "End date time cannot be null");

        // 自动处理时间范围顺序（若起始时间晚于结束时间则交换）
        if (compareStart.isAfter(compareEnd)) {
            LocalDateTime temp = compareStart;
            compareStart = compareEnd;
            compareEnd = temp;
        }

        // 判断是否在闭区间内 [compareStart, compareEnd]
        return !localDateTime.isBefore(compareStart) && !localDateTime.isAfter(compareEnd);
    }

    /**
     * 列出指定範圍內的工作日（排除週末 + 特殊日期）<br/>
     * 按日期升序順序排序
     *
     * @param start        開始
     * @param end          結束
     * @param excludeDates 排除日期（例如：節假日之類的特殊日期）
     * @return {@link List }<{@link LocalDate }>
     */
    public static List<LocalDate> listWorkDays(LocalDate start, LocalDate end, List<LocalDate> excludeDates) {
        Objects.requireNonNull(start, "Start date time cannot be null");
        Objects.requireNonNull(end, "End date time cannot be null");

        List<LocalDate> workDays = new ArrayList<>();
        if (start.isAfter(end)) return workDays;

        Set<LocalDate> excludeSet = (excludeDates != null) ? new HashSet<>(excludeDates) : Collections.emptySet();

        LocalDate current = start;
        while (!current.isAfter(end)) {
            if (isWorkday(current, excludeSet)) {
                workDays.add(current);
            }
            current = current.plusDays(1);
        }
        return workDays;
    }

    /**
     * 列出指定範圍內的所有日
     *
     * @param start 開始
     * @param end   結束
     * @return {@link List }<{@link LocalDate }>
     */
    public static List<LocalDate> listAllDays(LocalDate start, LocalDate end) {
        Objects.requireNonNull(start, "Start date time cannot be null");
        Objects.requireNonNull(end, "End date time cannot be null");

        List<LocalDate> allDates = new ArrayList<>();
        if (start.isAfter(end)) return allDates;

        LocalDate current = start;
        while (!current.isAfter(end)) {
            allDates.add(current);
            current = current.plusDays(1);
        }
        return allDates;
    }

    /**
     * 是否是工作日
     *
     * @param date       日期
     * @param excludeSet 排除日期（例如節假日之類）
     * @return boolean
     */
    public static boolean isWorkday(LocalDate date, Set<LocalDate> excludeSet) {
        return !isWeekend(date) && !excludeSet.contains(date);
    }

    /**
     * 是否是週末
     *
     * @param date 日期
     * @return boolean
     */
    public static boolean isWeekend(LocalDate date) {
        return date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY;
    }

    /**
     * 將指定格式的字串轉換為 LocalDate。
     *
     * @param dateString 日期字串，例如 "2023-10-26"
     * @param format     日期格式，例如 "yyyy-MM-dd"
     * @return /
     * @throws DateTimeParseException /
     */
    public static LocalDate str2LocalDate(String dateString, String format) {
        if (dateString == null || dateString.isEmpty()) {
            return null;
        }
        if (format == null || format.isEmpty()) {
            format = DTUtil.NORM_DATE_PATTERN;
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        return LocalDate.parse(dateString, formatter);
    }

    /**
     * 將指定格式的字串轉換為 LocalDateTime。
     *
     * @param dateTimeString 日期時間字串，例如 "2023-10-26 15:30:00"
     * @param format         日期時間格式，例如 "yyyy-MM-dd HH:mm:ss"
     * @return /
     * @throws DateTimeParseException /
     */
    public static LocalDateTime str2LocalDateTime(String dateTimeString, String format) {
        if (dateTimeString == null || dateTimeString.isEmpty()) {
            return null;
        }
        if (format == null || format.isEmpty()) {
            format = DTUtil.NORM_DATETIME_PATTERN;
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        return LocalDateTime.parse(dateTimeString, formatter);
    }

    /**
     * 獲取月末日期
     *
     * @param date 日期
     * @return {@link LocalDate }
     */
    public static LocalDate getEndOfMonthDate(LocalDate date) {
        if (date == null) return null;
        return date.with(TemporalAdjusters.lastDayOfMonth());
    }

    /**
     * 獲取月初日期
     *
     * @param date 日期
     * @return {@link LocalDate }
     */
    public static LocalDate getStartOfMonthDate(LocalDate date) {
        if (date == null) return null;
        return date.with(TemporalAdjusters.firstDayOfMonth());
    }

    /**
     * Date -> XXX.class
     *
     * @param date 日期
     * @param type 类型
     * @return {@link T }
     */
    private static <T> T toConvert(Date date, Class<T> type) {
        if (date == null) {
            return null;
        }
        Instant instant = date.toInstant();
        ZonedDateTime zonedDateTime = instant.atZone(DEFAULT_ZONE_ID);

        if (type == LocalDateTime.class) {
            return type.cast(zonedDateTime.toLocalDateTime());
        } else if (type == LocalDate.class) {
            return type.cast(zonedDateTime.toLocalDate());
        } else if (type == LocalTime.class) {
            return type.cast(zonedDateTime.toLocalTime());
        }
        throw new IllegalArgumentException("Unsupported type: " + type);
    }

    /**
     * 解析日期为字符串
     *
     * @param date    日期
     * @param pattern 日期格式
     * @return {@link String }
     */
    private static String formatDate(Date date, String pattern) {
        if (date == null) {
            return "";
        }
        LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), DEFAULT_ZONE_ID);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        return localDateTime.format(formatter);
    }

    /**
     * 某個瞬間
     *
     * @param instant 瞬間
     * @return {@link Date }
     */
    private static Date instant(Instant instant) {
        return Date.from(instant);
    }
}
